﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;

namespace Macds.Core.XML
{
    public class XmlClassBuilderManager
    {
        private List<ClassSchema> classes;
        private List<PropertySchema> properties;
        private ITextBuilder builder;
        private string xmlPath;
        private XElement xmlData;
        private int counter;

        public XmlClassBuilderManager(string xmlInfoStr)
        {
            classes = new List<ClassSchema>();
            properties = new List<PropertySchema>();
            builder = new DefaultClassBuilder();
            xmlPath = xmlInfoStr;
            RootClassType = "";
            counter = 0;
        }

        public XmlClassBuilderManager(ITextBuilder customBuilder, string xmlInfoStr)
        {
            classes = new List<ClassSchema>();
            properties = new List<PropertySchema>();
            builder = customBuilder;
            xmlPath = xmlInfoStr;
            RootClassType = "";
            counter = 0;
        }

        #region Public Properties

        public string RootClassType { get; set; }
        public bool IsXMLString { get; set; }

        #endregion

        #region Public Methods

        public string GetClassesAtOnce()
        {
            var data = GetClasses();

            var sb = new StringBuilder();
            foreach (var kv in data.Values)
            {
                sb.Append(kv + "\r\n\r\n");
            }

            return sb.ToString();
        }

        public Dictionary<string, string> GetClasses()
        {
            GetSchemaInformation();
            var list = new Dictionary<string, string>();

            foreach (var schema in classes)
            {
                list.Add(schema.Name, BuildClassText(schema));
            }

            return list;
        }

        public string BuildClassText(ClassSchema classSchema)
        {
            return builder.BuildClassText(classSchema);
        }

        public ClassSchema GetSchemaInformation()
        {
            if (IsXMLString)
            {
                xmlData = XElement.Parse(xmlPath);
            }
            else
            {
                xmlData = XElement.Load(xmlPath);
            }
            
            var root = RemoveAllNamespaces(xmlData);
            RootClassType = root.Name.LocalName;
            var classSchema = GetClassSchema(root);

            return classSchema;
        }

        public string RemoveAllNamespaces()
        {
            XElement xmlDocumentWithoutNs = RemoveAllNamespaces(xmlData);

            return xmlDocumentWithoutNs.ToString();
        }

        public XElement RemoveAllNamespaces(XElement xmlDocument)
        {
            if (!xmlDocument.HasElements)
            {
                XElement xElement = new XElement(xmlDocument.Name.LocalName);
                xElement.Value = xmlDocument.Value;

                foreach (XAttribute attribute in xmlDocument.Attributes())
                    xElement.Add(attribute);

                return xElement;
            }

            return new XElement(xmlDocument.Name.LocalName, xmlDocument.Elements().Select(el => RemoveAllNamespaces(el)));
        }

        public ClassSchema GetClassSchema(XElement root)
        {
            if (!String.IsNullOrEmpty(root.Name.LocalName))
            {
                // Create the main class
                var className = counter == 0 ? root.Name.LocalName : root.Name.LocalName + "Class";
                counter++;
                var main = new ClassSchema { Name = className };

                // Add class schema to the global classes holder
                if (classes.FirstOrDefault(c => c.Name.Trim().ToLower() == main.Name.Trim().ToLower()) == null)
                {
                    classes.Add(main);
                }

                #region Get property from value

                if (root.HasAttributes && !root.HasElements && String.IsNullOrEmpty(root.Value) == false)
                {
                    var prop = new PropertySchema { Name = "Value", Type = PropertyType.String, XmlType = PropertyXmlType.Text };

                    if (!main.PropertyExists(prop.Name))
                    {
                        main.Properties.Add(prop);

                        properties.Add(prop);
                    }
                }

                #endregion

                #region Get property from attributes

                if (root.HasAttributes)
                {
                    foreach (var propAttribute in root.Attributes())
                    {
                        var prop = new PropertySchema { Name = propAttribute.Name.LocalName, Type = PropertyType.String, XmlType = PropertyXmlType.Attribute };

                        if (!main.PropertyExists(prop.Name))
                        {
                            main.Properties.Add(prop);

                            properties.Add(prop);
                        }
                    }
                }

                #endregion

                #region Get property from elements

                if (root.HasElements)
                {
                    foreach (var propElement in root.Elements())
                    {
                        if (!main.PropertyExists(propElement.Name.LocalName))
                        {
                            var prop = GetPropertySchema(root, propElement);
                            main.Properties.Add(prop);

                            properties.Add(prop);
                        }
                    }
                }

                #endregion

                return main;
            }
            else
            {
                return null;
            }
        }

        public PropertySchema GetPropertySchema(XElement root, XElement propElement)
        {
            // General Details
            int total = root.Elements(propElement.Name).Count();
            bool hasAttribute = propElement.HasAttributes;
            bool hasElements = propElement.HasElements;
            bool hasValue = hasElements == false && String.IsNullOrEmpty(propElement.Value) == false ? true : false;

            var property = new PropertySchema { Name = propElement.Name.LocalName };
            property.Type = PropertyType.Custom;  

            # region Assign the property type

            // Check string type
            if (total == 1 && hasAttribute == false && hasElements == false)
            {
                property.Type = PropertyType.String;
            }
            else
            {
                // Check class type
                if (total == 1 && hasAttribute == true && hasElements == true)
                {
                    property.Type = PropertyType.Custom;
                }
                else
                {
                    // Check class type if the element has value and attribute
                    if (hasAttribute == true && hasValue == true)
                    {
                        property.Type = PropertyType.Custom;
                    }
                    else
                    {
                        // Check list type
                        if (total > 1)
                        {
                            property.Type = PropertyType.List;
                            property.XmlType = PropertyXmlType.Element;

                            if (hasAttribute || hasElements)
                            {
                                // Base type of the list is a custom class
                                property.ListType = PropertyType.Custom;
                            }
                            else
                            {
                                // Base type of the list is a string
                                property.ListType = PropertyType.String;

                            }
                        }
                    }
                }
            }

            #endregion

            #region Get class schema of the property

            if (property.Type == PropertyType.Custom || (property.Type == PropertyType.List && property.ListType == PropertyType.Custom))
            {
                property.CustomClass = GetClassSchema(propElement);
            }

            #endregion

            return property;
        }

        #endregion
    }
}
